#include "tdp2_liveresource.hpp"
#include "tdp2_maincontext.hpp"
#include "tdp2_renderdevice.hpp"

using namespace RDX::ObjectManagement;

namespace TDP
{
	namespace Resources
	{
		LiveResourceManager::LiveResourceManager(RDX::ObjectManagement::Allocator &alloc, TDP::MainContext *mainContext)
			: _lrLinks(alloc), _preloadBlock(alloc)
		{
			_alloc = alloc;
			_mainContext = mainContext;
			_preloadBlock.SetAlign(RDX_MAX_ALIGNMENT);
			_preloadNumBytes = 0;
		}

		bool LiveResourceManager::Initialize(LargeInt maxResources, LargeInt maxBlockSize)
		{
			if(!_lrLinks.TryResize(maxResources))
				return false;
			if(!_preloadBlock.TryResize(maxBlockSize))
				return false;

			for(LargeInt i=0;i<maxResources;i++)
			{
				_lrLinks[i].nextFree = _lrLinks + i + 1;
				_lrLinks[i].resource = NULL;
			}
			_lrLinks[maxResources-1].nextFree = NULL;
			_firstFree = _lrLinks;

			return true;
		}

		LiveResourceHandle LiveResourceManager::CreateTexture(TextureSpec::TextureFormat tf,
				TextureSpec::TextureLayout tl, Int numMipMaps, Int cachedMipMaps,
				Int width, Int height, Int depth, UInt32 flags)
		{
			if(numMipMaps < 1 || width < 1 || height < 1 || depth < 1)
				return 0;

			LiveResourceHandle lrh = CreateLRH(sizeof(LiveTextureResource), RT_Texture);
			if(!lrh)
				return 0;
			Rendering::IRenderDevice *device = _mainContext->RenderDevice();

			// Initialize
			LiveTextureResource *ltr = static_cast<LiveTextureResource *>(MapLRH(lrh));
			new (ltr) LiveTextureResource(device, tf, tl, numMipMaps, cachedMipMaps, width, height, depth, flags);

			return lrh;
		}

		LiveResourceHandle LiveResourceManager::CreateCompiledShader(ShaderSpec::ShaderType shaderType, LargeUInt sizeBytes)
		{
			if(sizeBytes < 1 || sizeBytes > RDX_LARGEINT_MAX)
				return 0;

			LiveResourceHandle lrh = CreateLRH(sizeof(LiveTextureResource), RT_Texture);
			if(!lrh)
				return 0;
			Rendering::IRenderDevice *device = _mainContext->RenderDevice();

			// Initialize
			LiveCompiledShaderResource *lcsr = static_cast<LiveCompiledShaderResource *>(MapLRH(lrh));
			new (lcsr) LiveCompiledShaderResource(device, _alloc, shaderType, sizeBytes);

			return lrh;
		}

		bool LiveResourceManager::TryStream(ResourceManagement::ResourcePreloader *preloader, Resources::StreamableResource *sr)
		{
			switch(sr->_native.lrhType)
			{
			case RT_Bundle:
				{
					const ResourceBundle *bundle = static_cast<const ResourceBundle *>(sr);
					if(bundle->resources == NULL)
						return false;	// Bad data

					LargeInt numResources = GCInfo::From(bundle->resources)->numElements;
					if(_bundleResourceIndex == numResources)
						return false;	// Exhausted the bundle

					BundledResource *br = bundle->resources[_bundleResourceIndex];
					if(br == NULL || br->_native.lrhType == RT_Bundle)
						return false;	// Bad data

					if(_bundleResourceStarted == false)
					{
						BeginStreamingResource(preloader, br, true);
						_bundleResourceStarted = true;
					}

					bool resResult = TryStream(preloader, br);
					if(!resResult)
					{
						// Exhausted the resource
						_bundleResourceIndex++;
						_bundleResourceStarted = false;
						if(_bundleResourceIndex == numResources)
							return false;	// Exhausted the bundle
					}
					return true;
				}
				break;
			case RT_Texture:
			case RT_CompiledShader:
			case RT_Geometry:
				{
					if(sr->_native.resHandle == 0)
						return false;	// Bad resource?

					// TODO: Make sure preloading has definitely started before reaching here
					LargeUInt available = _preloadBlock.Count() - _preloadNumBytes;
					if(available)
					{
						LargeUInt numRead;
						bool more = preloader->Read(_preloadBlock + _preloadNumBytes, 1, available, &numRead);
						if(!more && _preloadNumBytes == 0)
							return false;	// Exhausted early
						_preloadNumBytes += numRead;
					}

					LargeUInt numDigested;
					bool resMore = MapLRH(sr->_native.resHandle)->DigestStreamData(_preloadBlock, _preloadNumBytes, &numDigested);
					_preloadNumBytes -= numDigested;
					if(_preloadNumBytes != 0)
					{
						// Didn't digest everything, move the buffer back
						// NOTE: Already subtracted digested count
						memmove(_preloadBlock, _preloadBlock + numDigested, _preloadNumBytes);
					}
					return resMore;
				}
				break;
			}

			return false;
		}

		void LiveResourceManager::BeginStreamingResource(ResourceManagement::ResourcePreloader *preloader, Resources::StreamableResource *sr, bool fromBundle)
		{
			switch(sr->_native.lrhType)
			{
			case RT_Bundle:
				{
					_bundleResourceIndex = 0;
					_bundleResourceStarted = false;
					ResourceBundle *bundle = static_cast<ResourceBundle *>(sr);

					preloader->BeginStreamingFile(bundle->binaryFileName->AsChars(), false);
				}
				break;
			case RT_Texture:
				{
					BundledTexture *bt = static_cast<BundledTexture *>(sr);
					if(bt->_native.resHandle == 0)
					{
						UInt32 flags = 0;
						if(bt->autoMipMap)
							flags |= Rendering::TXF_AutoMipMap;
						if(bt->sRGB)
							flags |= Rendering::TXF_sRGB;
						if(!bt->_native.resHandle)
							bt->_native.resHandle = CreateTexture(static_cast<TextureSpec::TextureFormat>(bt->format.Value()), static_cast<TextureSpec::TextureLayout>(bt->layout.Value()),
								bt->numMipMaps, bt->cachedMipMaps, bt->width, bt->height, bt->depth, flags);

						static_cast<LiveTextureResource *>(MapLRH(bt->_native.resHandle))->StartStreaming(preloader, bt->cachedMipMaps);
					}
				}
				break;
			case RT_CompiledShader:
				{
					BundledCompiledShader *bcs = static_cast<BundledCompiledShader *>(sr);
					if(bcs->_native.resHandle == 0)
						bcs->_native.resHandle = CreateCompiledShader(static_cast<ShaderSpec::ShaderType>(bcs->shaderType.Value()), bcs->sizeBytes);
					static_cast<LiveCompiledShaderResource *>(MapLRH(bcs->_native.resHandle))->StartStreaming();
				}
				break;
			case RT_Geometry:
				{
				}
				break;
			default:
				break;
			}
		}
		
		LiveResourceHandle LiveResourceManager::CreateLRH(size_t dataSize, size_t align)
		{
			if(!_firstFree)
				return 0;

			LiveResourceLink *lrl = _firstFree;
			LiveResource *resBuf = static_cast<LiveResource *>(_alloc.reallocFunc(_alloc.opaque, NULL, dataSize, align));
			if(!resBuf)
				return 0;
			// Allocated OK
			lrl->resource = resBuf;
			resBuf->_initialized = false;
			_firstFree = lrl->nextFree;
			return lrl - this->_lrLinks + 1;
		}

		// Accesses the memory of a LRH.  CreateLRH should never be called while using a mapped handle.
		LiveResource *LiveResourceManager::MapLRH(LiveResourceHandle lrh)
		{
			if(lrh == 0)
				return NULL;
			return this->_lrLinks[lrh - 1].resource;
		}


		void LiveResourceManager::ReleaseLRH(LiveResourceHandle lrh)
		{
			if(lrh == 0)
				return;

			LiveResourceLink *resLink = _lrLinks + lrh - 1;
			if(resLink->resource != NULL && resLink->resource->_initialized)
				resLink->resource->~LiveResource();
			resLink->resource = NULL;
			resLink->nextFree = _firstFree;
			_firstFree = resLink;
		}

		LiveResourceManager::~LiveResourceManager()
		{
			LargeUInt numLinks = _lrLinks.Count();
			for(LargeUInt i=0;i<numLinks;i++)
			{
				if(_lrLinks[i].resource)
					_lrLinks[i].resource->~LiveResource();
				_lrLinks[i].resource = NULL;
			}
		}

		LiveResource::LiveResource()
		{
			this->_initialized = false;
		}

		LiveResource::~LiveResource()
		{
		}
	}
}